package org.chintoooor.trio.server.http.task;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;

import org.chintoooor.trio.server.http.HttpParser;
import org.chintoooor.trio.server.http.HttpRequest;
import org.chintoooor.trio.server.http.HttpResponse;
import org.chintoooor.trio.server.http.processor.DummyResponseProcessor;
import org.chintoooor.trio.server.http.processor.HttpRequestProcessor;

public class Http implements Runnable {

	private final Logger logger = Logger.getLogger(Http.class
			.getCanonicalName());

	private final AtomicBoolean isUp;
	private final HttpTaskModel listenerThreadModel;
	private final CharsetDecoder utf8CharsetDecoder;
	private final CharsetEncoder utf8CharsetEncoder;
	private final Charset utf8Charset;
	private final ByteBuffer httpRequestBytesBuffer;

	public Http(AtomicBoolean isUp, HttpTaskModel selectionModel) {
		super();
		this.isUp = isUp;
		this.listenerThreadModel = selectionModel;

		this.httpRequestBytesBuffer = ByteBuffer.allocate(4096);
		this.utf8Charset = Charset.forName("UTF-8");
		this.utf8CharsetDecoder = utf8Charset.newDecoder();
		this.utf8CharsetEncoder = utf8Charset.newEncoder();
	}

	@Override
	public void run() {
		logger.entering(Http.class.getCanonicalName(), "run");
		try {
			while (isUp.get()) {
				Selector selector = listenerThreadModel.getSelector();
				selector.select();
				Set<SelectionKey> readAndWriteKeys = selector.selectedKeys();
				Iterator<SelectionKey> readAndWriteKeysIterator = readAndWriteKeys
						.iterator();
				while (readAndWriteKeysIterator.hasNext()) {
					SelectionKey currentReadWriteKey = readAndWriteKeysIterator
							.next();
					if ((currentReadWriteKey.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {

						SocketChannel readWriteSocketChannel = (SocketChannel) currentReadWriteKey
								.channel();

						logger.fine("reading HTTP from "
								+ readWriteSocketChannel);

						StringBuilder contentsStringBuilder = new StringBuilder(
								4096);

						int bytesRead = 0;
						while ((bytesRead = readWriteSocketChannel
								.read(httpRequestBytesBuffer)) > 0) {
							httpRequestBytesBuffer.rewind();
							contentsStringBuilder.append(utf8CharsetDecoder
									.decode(httpRequestBytesBuffer));

							logger.finest("The bytes length received is "
									+ bytesRead + ", bytes are "
									+ httpRequestBytesBuffer);
						}

						String requestStringAfterTrim = contentsStringBuilder
								.toString().trim();
						logger.finer("The request received is"
								+ requestStringAfterTrim);

						HttpParser httpParser = new HttpParser();
						HttpRequest httpRequest = httpParser
								.parseRequest(requestStringAfterTrim);
						logger.fine("The parsed request received is "
								+ httpRequest);

						HttpRequestProcessor requestProcessor = new DummyResponseProcessor();
						HttpResponse httpResponse = requestProcessor
								.process(httpRequest);

						CharBuffer httpRequestCharBuffer = CharBuffer
								.wrap(httpParser.encodeResponse(httpResponse));

						ByteBuffer httpResponseByteBuffer = utf8CharsetEncoder
								.encode(httpRequestCharBuffer);

						readWriteSocketChannel.write(httpResponseByteBuffer);

						readWriteSocketChannel.close();
						currentReadWriteKey.cancel();

						httpRequestBytesBuffer.clear();
						readAndWriteKeysIterator.remove();
					}
				}
				if (readAndWriteKeys.isEmpty()
						&& listenerThreadModel.hasUnregisteredChannels()) {
					logger.fine("Registering unregistered channels");
					listenerThreadModel.registerAll();
				}
			}
		} catch (IOException ioException) {
			logger.warning("The thread will now exit due to " + ioException);
			throw new RuntimeException(ioException);
		} catch (RuntimeException runtimeException) {
			logger.warning("The thread will now exit due to "
					+ runtimeException);
			throw runtimeException;
		}
		logger.exiting(Http.class.getCanonicalName(), "run");
	}
}
